/////////////////////////////////////////////////////////////////////////
//
// Amuse Engine SDK - core/system/io
// Copyright (c) 2014.  All Rights Reserved
//
// File:		AEFileSystem.h
// Author:		Gianluca Belardelli
// Date:		08/10/2013
//
/////////////////////////////////////////////////////////////////////////
#ifndef _AEFILESYSTEM_H_
#define _AEFILESYSTEM_H_

class AEFileSystem : public AEReferencedObject, public AESingleton<AEFileSystem>
{
	//AE_DECLARE_SINGLETON( AEFileSystem );

// Members
public:
	enum AEFileAccessMode
	{
		AE_ACCESS_READ = 1,
		AE_ACCESS_WRITE = 2,
	};

	enum Result
	{
		RESULT_OK = 0, ///< Success
		RESULT_ERROR, ///< Unspecified error
		RESULT_NOT_IMPLEMENTED, ///< Method is not implemented
	};

	enum AEFileOpenFlags
	{
		/// The file is buffered. This makes peek() available and takes care of any platform-specific
		/// alignment and block size requirements.
		AE_OPEN_BUFFERED = 1,
		/// Truncate the file if it exists. Without this flag, the content (if any) is not
		/// truncated. In either case, the initial file offset will always be zero, so a seek is
		/// required to append data.
		AE_OPEN_TRUNCATE = 4,	
		/// Default read mode
		AE_OPEN_DEFAULT_READ = AE_OPEN_BUFFERED,
		/// Default write mode
		AE_OPEN_DEFAULT_WRITE = AE_OPEN_BUFFERED | AE_OPEN_TRUNCATE,
	};

	/// Platform independent timestamp that can be used to reference points in time.
	/// Stores timestamp in nanoseconds that have passed since
	/// 1970-01-01T00:00:00.000 UTC (Unix time in nanoseconds).
	/// Can represent valid ranges in (1970-01-01T00:00:00.000 UTC, 2554-07-21T23:34:33 UTC].
	struct AETimeStamp
	{
		private:
			AEUINT64 m_ullTime;

		public:
			enum { AE_TIMESTAMP_UNAVAILABLE = 0 };

			AETimeStamp() : m_ullTime( AE_TIMESTAMP_UNAVAILABLE ) {}
			AETimeStamp( AEUINT64 ullNsSinceEpoch ) : m_ullTime( ullNsSinceEpoch ) {}

			AE_FORCEINLINE bool operator==( const AETimeStamp &tsRhs ) const { return m_ullTime == tsRhs.m_ullTime; }
			AE_FORCEINLINE bool operator<( const AETimeStamp &tsRhs ) const { return m_ullTime < tsRhs.m_ullTime; }
			AE_FORCEINLINE bool operator>( const AETimeStamp &tsRhs ) const { return m_ullTime > tsRhs.m_ullTime; }

			/// Sets the timestamp to the given Unix time in nanoseconds.
			void Set( AEUINT64 ullNsSinceEpoch );

			/// Returns the nanoseconds passed since Unix epoch.
			AEUINT64 Get( void ) const;

			/// Returns whether the stored time is valid.
			/// Invalid values are returned by file systems that do not support timestamps.
			bool IsValid( void ) const;
	};

			/// Represents a file or directory in a filesystem.
	struct AEFileOrDirEntry
	{
		// Members
		public:
			enum AEFlagValues
			{
				AE_ISFILE = 1,		///< Regular file
				AE_ISDIR = 2,		///< Folder type
				AE_ISUNKNOWN = 4,	///< Unrecognised type (e.g. link, device)
			};

			typedef AEFlags<AEFlagValues, AEUINT32> FileFlags;

		private:
			AEFileSystem	*m_lpFS;		///< The filesystem this entry belongs to.
			char			*m_lpPath;		///< Path to the entry including the name.
			AETimeStamp		m_tsModTime;	///< Last modified time.
			AEINT64			m_ullSize;		///< Number of bytes in this entry or -1 if unknown.
			FileFlags		m_ffFlags;		///< Flags (isdir, isfile etc)

		// Methods
		public:
			AE_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR( TOBEDEFINED, AEFileSystem::AEFileOrDirEntry );

			AEFileOrDirEntry( void ) : m_lpFS(0), m_tsModTime(), m_ullSize(-1), m_ffFlags(0) {}

			AEUINT32 IsDir( void ) const { return m_ffFlags.Get( AE_ISDIR ); }
			AEUINT32 IsFile( void ) const { return m_ffFlags.Get( AE_ISFILE ); }
			const char*GetPath( void ) const { return m_lpPath; }
			const char *GetName( void ) const;
			AETimeStamp GetLastModifiedTime( void ) const { return m_tsModTime; }
			AEINT64 GetSize( void ) const { return m_ullSize; }

			void SetPath( AEFileSystem *lpFS, const char *lpPath );
			void SetModifiedTime( AETimeStamp tsTime ) { m_tsModTime = tsTime; }
			void SetSize( AEUINT64 ullSize ) { m_ullSize = ullSize; }
			void SetFlags( FileFlags ffFlags ) { m_ffFlags = ffFlags; }
			void SetAll( AEFileSystem *lpFS, const char *lpFullPath, FileFlags ffFlags, AETimeStamp tsModTime, AEUINT64 ullSize );
				
			AERefNew<AEStreamReader> OpenReader( AEFileOpenFlags fofFlags = AE_OPEN_DEFAULT_READ ) const;
			//AERefNew<AEStreamWriter> OpenWriter( AEFileOpenFlags fofFlags = AE_OPEN_DEFAULT_WRITE ) const;
	};

// Methods
public:
	AE_DECLARE_CLASS_ALLOCATOR(TOBEDEFINED);

	/// Returns a stream reader for file 'name' or null if unable.
	virtual AERefNew<AEStreamReader> OpenReader( const char *lpFileName, AEFileOpenFlags fofFlags = AE_OPEN_DEFAULT_READ ) = 0;

	/// Returns a stream reader for file 'name' or null if unable.
	//virtual AERefNew<AEStreamWriter> OpenWriter( const char *lpFileName, AEFileOpenFlags fofFlags = AE_OPEN_DEFAULT_WRITE ) = 0;

	/// Remove the given path.
	virtual Result Remove(const char* path) { return RESULT_NOT_IMPLEMENTED; }

	/// Create a folder with the given name.
	virtual Result Mkdir(const char* path) { return RESULT_NOT_IMPLEMENTED; }

	/// Look up a single named path, returns true if an entry exists.
	virtual Result Stat( const char *lpPath, AEFileOrDirEntry &entryOut ) = 0;
/*
			/// Interface to iterate over the entries in a filesystem tree.
		class Iterator
		{
			public:

				HK_DECLARE_PLACEMENT_ALLOCATOR();

					/// Construct an iterator at the given folder name.
				Iterator(hkFileSystem* fs, const char* folder, const char* wildcard=HK_NULL);

					/// Attempt to advance to the next entry.
					/// Return true if there is an entry or return false if there are no more entries.
				bool advance();

					/// Access the current entry. This is only valid after a call to advance() returns true.
				const Entry& current() const { return m_entry; }

					/// Recurse into the given path when the current iterator is exhausted.
				void recurseInto(const char* path)
				{
					m_todo.pushBack(path);
				}

					/// Return true if the name matches the wildcard and is not "." nor "..".
					/// A null wildcard is considered to match everything.
				static bool nameAcceptable(const char* name, const char* wildcard);

					// Internal interface for filesystem
				struct Impl : public hkReferencedObject
				{
					HK_DECLARE_CLASS_ALLOCATOR(HK_MEMORY_CLASS_STREAM);
					virtual bool advance(Entry& e) = 0;
				};

			private:

				hkRefPtr<hkFileSystem> m_fs;
				const char* m_wildcard;
				hkRefPtr<Impl> m_impl;
				Entry m_entry;
				hkArray<hkStringPtr> m_todo;
		};

			// Internal function used by Iterator. Use the Iterator class instead.
		virtual hkRefNew<Iterator::Impl> createIterator( const char* top, const char* wildcard ) = 0;

	public:

		struct DirectoryListing
		{
			HK_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR(HK_MEMORY_CLASS_BASE, hkFileSystem::DirectoryListing);

			DirectoryListing(hkMemoryAllocator* alloc, const char* top=HK_NULL)
				: m_fs(HK_NULL), m_top(top)
			{
				m_entries.reserve(32);
			}

			void clear()
			{
				m_entries.clear();
			}

			void addEntry(const Entry& ent) 
			{
				HK_ASSERT(0x76231193, m_fs);
				m_entries.pushBack(ent);
			}

			void addDirectory(const char* name) 
			{
				HK_ASSERT(0x5d400f6f, m_fs);
				m_entries.expandOne().setAll( m_fs, hkStringBuf(m_top.cString()).pathAppend(name), Entry::F_ISDIR, TimeStamp(), 0);
			}

				/// adds a copy of name to the file names list.
				/// Also stores the last modified time stamp (default 0).
			void addFile(const char* name, TimeStamp timeModified, hkInt64 size=-1) 
			{
				HK_ASSERT(0x7a9590f9, m_fs);
				m_entries.expandOne().setAll(m_fs, hkStringBuf(m_top.cString()).pathAppend(name), Entry::F_ISFILE, timeModified, size);
			}

			const hkArrayBase<Entry>& getEntries() const
			{
				return m_entries;
			}

			void setFs( hkFileSystem* fs ) { m_fs = fs; }

			hkBool isEmpty() const { return m_entries.isEmpty(); }

		private:

			hkArray<Entry> m_entries;
			hkFileSystem* m_fs;
			hkStringPtr m_top;
		}; // DirectoryListing
		
			/// Deprecated: use Iterator instead.
			/// List all the directories and files in the "path" directory, returns HK_FAILURE if the path is not valid.
		hkResult listDirectory(const char* basePath, DirectoryListing& listingOut);
		*/
	protected:

		//hkStreamReader* _handleFlags(hkStreamReader* sr, OpenFlags flags);
		//hkStreamWriter* _handleFlags(hkStreamWriter* sw, OpenFlags flags);
};

#endif // _AEFILESYSTEM_H_
